package cn.mini.mybatis.mybatis.binding;

import cn.mini.mybatis.mybatis.mapping.MappedStatement;
import cn.mini.mybatis.mybatis.mapping.SqlCommandType;
import cn.mini.mybatis.mybatis.session.SqlSession;
import cn.mini.mybatis.mybatis.session.Configuration;

import java.lang.reflect.Method;
import java.util.*;

/**
 * MethodSignature 是一个关键组件，负责解析 Mapper 接口方法的元数据并将其转换为
 * MyBatis 可执行的指令。它的核心作用是将 Java 方法的参数和返回值映射到 SQL 执行所需的格式，
 * 从而连接接口调用与 SQL 执行逻辑
 * @author 毅航
 * @date 2025/6/22 10:27
 */
public class MapperMethod {
    /**
     * 封装了SQL指令
     */
    private final SqlCommand command;
    /**
     * 封装了方法参数
     */
    private final MethodSignature methodSignature;
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
        this.command = new SqlCommand(configuration, mapperInterface, method);
        this.methodSignature = new MethodSignature(configuration, method);
    }
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = methodSignature.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = methodSignature.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = methodSignature.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                Object param = methodSignature.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                // 暂时省略对Map结构查询的支持
                break;
            default:
                throw new IllegalStateException("Unknown execution method for: " + command.getName());
        }
        return result;
    }

    private Object rowCountResult(int rowCount) {
        final Object result;
        if (methodSignature.returnsVoid()) {
            result = null;
        } else if (Integer.class.equals(methodSignature.getReturnType()) || Integer.TYPE.equals(methodSignature.getReturnType())) {
            //如果返回值是大int或小int
            result = Integer.valueOf(rowCount);
        } else if (Long.class.equals(methodSignature.getReturnType()) || Long.TYPE.equals(methodSignature.getReturnType())) {
            //如果返回值是大long或小long
            result = Long.valueOf(rowCount);
        } else if (Boolean.class.equals(methodSignature.getReturnType()) || Boolean.TYPE.equals(methodSignature.getReturnType())) {
            //如果返回值是大boolean或小boolean
            result = Boolean.valueOf(rowCount > 0);
        } else {
            throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + methodSignature.getReturnType());
        }
        return result;
    }

    /**
     * SqlCommand 类用于封装 SQL 命令的相关信息，包括 SQL 语句的名称和命令类型。
     */
    public static class SqlCommand {
        /**
         * SQL 语句的唯一标识符
         */
        private final String name;
        /**
         * SQL 命令的类型，如 INSERT、UPDATE、DELETE、SELECT 等
         */
        private final SqlCommandType type;

        /**
         * 构造函数，根据配置、Mapper 接口和方法信息初始化 SQL 命令。
         *
         * @param configuration    MyBatis 的配置对象，包含了所有的映射信息
         * @param mapperInterface  Mapper 接口的 Class 对象
         * @param method           Mapper 接口中的方法
         */
        public SqlCommand(Configuration configuration,Class<?> mapperInterface, Method method) {
            // 生成 SQL 语句的唯一标识，格式为 "Mapper 接口全限定名.方法名"
            String statementName = mapperInterface.getName() + "." + method.getName();
            // 声明 MappedStatement 对象，用于存储 SQL 语句的映射信息
            MappedStatement ms;
            // 检查配置中是否存在该 SQL 语句
            if (configuration.hasStatement(statementName)) {
                // 若存在，则从配置中获取对应的 MappedStatement 对象
                ms = configuration.getMappedStatement(statementName);
            } else {
                // 若方法是 Object 类中的方法，抛出异常
                if (method.getDeclaringClass() == Object.class) {
                    throw new RuntimeException("Object method: " + method.getName());
                } else {
                    // 若配置中不存在该 SQL 语句，抛出异常
                    throw new RuntimeException("Unknown statement: " + statementName);
                }
            }
            // 若 MappedStatement 对象为空，抛出绑定异常
            if (ms == null) {
                throw new BindingException("Invalid bound statement (not found): " + statementName);
            }
            // 从 MappedStatement 对象中获取 SQL 语句的唯一标识符
            name = ms.getId();
            // 从 MappedStatement 对象中获取 SQL 命令的类型
            type = ms.getSqlCommandType();
            // 若 SQL 命令类型为 UNKNOWN，抛出绑定异常
            if (type == SqlCommandType.UNKNOWN) {
                throw new BindingException("Unknown execution method for: " + name);
            }
        }

        /**
         * 获取 SQL 语句的唯一标识符
         *
         * @return SQL 语句的唯一标识符
         */
        public String getName() {
            return name;
        }

        /**
         * 获取 SQL 命令的类型
         *
         * @return SQL 命令的类型
         */
        public SqlCommandType getType() {
            return type;
        }
    }


    /**
     * MethodSignature 类用于解析 Mapper 接口方法的签名信息，包括返回类型、参数信息等，
     * 并提供将方法参数转换为 SQL 命令所需参数的功能。
     */
    public static class MethodSignature {

        /**
         * 方法的返回类型
         */
        private final boolean returnsVoid;
        private final Class<?> returnType;
        /**
         * 方法参数的有序映射，键为参数位置，值为参数名称
         */
        private final SortedMap<Integer, String> params;

        /**
         * 构造函数，根据配置和方法信息初始化方法签名。
         *
         * @param configuration MyBatis 的配置对象
         * @param method        Mapper 接口中的方法
         */
        public MethodSignature(Configuration configuration, Method method) {
            // 获取方法的返回类型
            this.returnType = method.getReturnType();
            this.returnsVoid = void.class.equals(this.returnType);
            // 获取方法参数的有序映射，并设置为不可修改
            this.params = Collections.unmodifiableSortedMap(getParams(method));
        }

        /**
         * 将方法调用的参数转换为 SQL 命令所需的参数对象。
         *
         * @param args 方法调用时传入的参数数组
         * @return 转换后的参数对象
         */
        public Object convertArgsToSqlCommandParam(Object[] args) {
            // 获取参数的数量
            final int paramCount = params.size();
            if (args == null || paramCount == 0) {
                // 如果没有参数，返回 null
                return null;
            } else if (paramCount == 1) {
                // 如果只有一个参数，返回该参数
                return args[params.keySet().iterator().next().intValue()];
            } else {
                // 否则，返回一个 ParamMap，修改参数名，参数名就是其位置
                final Map<String, Object> param = new ParamMap<>(args);
                int i = 0;
                // 遍历参数映射
                for (Map.Entry<Integer, String> entry : params.entrySet()) {
                    // 1.先加一个 #{0},#{1},#{2}... 参数
                    param.put(entry.getValue(), args[entry.getKey().intValue()]);
                    // issue #71, add param names as param1, param2...but ensure backward compatibility
                    final String genericParamName = "param" + (i + 1);
                    if (!param.containsKey(genericParamName)) {
                        /*
                         * 2.再加一个 #{param1},#{param2}... 参数
                         * 你可以传递多个参数给一个映射器方法。如果你这样做了,
                         * 默认情况下它们将会以它们在参数列表中的位置来命名,比如: #{param1},#{param2} 等。
                         * 如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用 @Param(“paramName”) 注解。
                         */
                        param.put(genericParamName, args[entry.getKey()]);
                    }
                    i++;
                }
                return param;
            }
        }

        /**
         * 获取方法参数的有序映射。
         *
         * @param method Mapper 接口中的方法
         * @return 方法参数的有序映射，键为参数位置，值为参数名称
         */
        private SortedMap<Integer, String> getParams(Method method) {
            // 用一个 TreeMap，这样就保证还是按参数的先后顺序
            final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
            // 获取方法的参数类型数组
            final Class<?>[] argTypes = method.getParameterTypes();
            for (int i = 0; i < argTypes.length; i++) {
                // 参数名称为当前参数的位置
                String paramName = String.valueOf(params.size());
                params.put(i, paramName);
            }
            return params;
        }


        public boolean returnsVoid() {
            return returnsVoid;
        }

        public Class<?> getReturnType() {
            return returnType;
        }

    }

    public static class ParamMap<T> extends HashMap<String, T> {
        private static final long serialVersionUID = -2212268410512043556L;

        public ParamMap(Object[] args) {
            // todo 后续支持 @Param 注解 处理
            // 目前仅是参数顺序的拼接  param1,param2,param3
            for (int i = 0; i < args.length; i++) {
                put("param" + (i + 1), (T) args[i]);
            }
        }
    }
}
